#include <assert.h>
#include "view.h"
#include "vievents.h"
#include "visoal.h"
#include "region.h"
#include "action.h"

static sync_object_t view_ops_sync;

// extern functions
/* view operations */
view_t* view_alloc(void);
void view_free(view_t* v);

//-----------

static view_do_setparent(view_t* parent, view_t* vi)
{
	// need lock
	sync_start(&view_ops_sync);

	// add as a child
	vi->parent = parent;
	listnode_add(parent->children, vi->ownerlist);

	// unlock
	sync_end(&view_ops_sync);
}

view_t* view_create(view_t* parent, view_desc_t* desc, void* data)
{
	view_t *ret;

	if (NULL == desc)
		return NULL;

	ret = view_alloc();
	if (NULL == ret) return NULL;
	ret->desc = desc;
	ret->data = data;

	if (parent)
		view_do_setparent(parent, ret);

	view_notify_event(ret, view_ev_create_event, NULL);
	return ret;
}

int view_setparent(view_t* parent, view_t* vi)
{
	if (!parent || !vi)
		return 1;
	if (vi->parent)
		return 2;

	view_do_setparent(parent, vi);
	// todo:
	return 0;
}

// this function is not locked
_bool view_can_render(view_t* vi)
{
	assert(NULL != vi);
	for (; vi; vi = vi->parent)
	{
		if (!(vi->flags & VW_FLG_SHOW))
			return false;
	}
	return true;
}

// this function is not locked
void view_render_region(listnode_t* head, view_draw_reason_e r)
{
	while (!listnode_isempty(*head))
	{
		listnode_t *node = head->next;
		region_t* rgn = list_entry(region_t, ownerlist, node);
		view_t* vi = rgn->vi;
		listnode_del(rgn->ownerlist);
		if (!vi) continue;

		region_normalize(vi, rgn);
		vi->desc->draw_handler(r, vi, rgn);
		region_free(rgn);
	}
}

static void view_setpos_invalidate(view_t* vi, rect_t* _old, rect_t* _new)
{
	region_t* r;
	listnode_t head;

	assert(vi && _old && _new);

	r = region_create();
	if (NULL == r) return;
	region_addrect(r, _old->left, _old->top, _old->width, _old->height);
	region_subtract_rect(r, _new->left, _new->top, _new->width, _new->height);
	if (region_isempty(*r))
	{
		region_free(r);
		return;
	}

	// do the invalidate operation
	invalidate(vi, r, &head);
	region_free(r);
	view_render_region(&head, vw_draw_reason_show); // todo: reason
}

// this function is not locked
static void view_notify_resize_event(view_t* vi, rect_t* orig)
{
	view_ev_resize_data_t data;

	data.orig_width = orig->width;
	data.orig_height = orig->height;

	view_notify_event(vi, view_ev_resize_event, &data);
}

static void view_notify_move_event(view_t* vi, rect_t* orig)
{
	view_ev_move_data_t data;

	data.orig_left = orig->left;
	data.orig_top = orig->top;

	view_notify_event(vi, view_ev_move_event, &data);
}

static void add_setpos_action(view_t* vi, rect_t* r1)
{
	view_action_data_t data;

	if (!view_can_render(vi))
		return;

	data.actionid = vi_actionid_move_resize;
	data.u.move_resize.orig_left = r1->left;
	data.u.move_resize.orig_top = r1->top;
	data.u.move_resize.orig_width = r1->width;
	data.u.move_resize.orig_height = r1->height;
	view_add_action(vi, &data);
}

int view_setpos(view_t* vi, int left, int top, int width, int height)
{
	rect_t r1, r2;

	if (NULL == vi)
		return 1;
	if (width < 0 || height < 0)
		return 2;

	// need sync
	sync_start(&view_ops_sync);

	set_rect(r1, vi->left, vi->top, vi->width, vi->height);
	set_rect(r2, left, top, width, height);

	if (rect_equal(r1, r2))
	{
		// nothing need to be done
		sync_end(&view_ops_sync);
		return 0;
	}

	// set the new position
	vi->left = left;
	vi->top = top;
	vi->width = width;
	vi->height = height;

	// event notification
	if (r1.left != r2.left || r1.top != r2.top)
		view_notify_move_event(vi, &r1);
	if (r1.width != r2.width || r1.height != r2.height)
		view_notify_resize_event(vi, &r1);

	add_setpos_action(vi, &r1);
	// unlock
	sync_end(&view_ops_sync);
	return 0;
}

view_t* get_top_child_view(view_t* vi)
{
	while (vi)
	{
		listnode_t *c;
		if (listnode_isempty(vi->children))
			return vi;
		c = vi->children.prev;
		vi = list_entry(view_t, ownerlist, c);
	}
	return NULL;
}

static void add_setshow_action(view_t* vi, _bool bshow)
{
	view_action_data_t data;
	if (vi->parent && !view_can_render(vi->parent))
		return;

	data.actionid = (bshow) ? vi_actionid_show : vi_actionid_hide;
	view_add_action(vi, &data);
}

void view_setshow(view_t* vi, _bool bshow)
{
	_bool show_stat;
	listnode_t head = LISTNODE_INITIALIZER(head);

	if (!vi) return;

	// need lock
	sync_start(&view_ops_sync);

	show_stat = view_getshow(vi) ? true : false;
	if (show_stat == bshow)
		goto setshow_ret;

	// set the view show / hide
	if (bshow)
	{
		vi->flags |= VW_FLG_SHOW;
		add_setshow_action(vi, true);
	}
	else
	{
		vi->flags &= ~VW_FLG_SHOW;
		add_setshow_action(vi, false);
	}

setshow_ret:
	sync_end(&view_ops_sync);
}

view_t* view_find_parent_by_classid(view_t* vi, viewid_t vid)
{
	if (NULL == vi || NULL == vid)
		return NULL;

	// need lock
	sync_start(&view_ops_sync);
	for (; vi; vi = vi->parent)
	{
		view_desc_t* desc = vi->desc;
		assert(NULL != desc);
		if (desc->viewid != vid)
		{
			// unlock
			sync_end(&view_ops_sync);
			return vi;
		}
	}
	sync_end(&view_ops_sync);
	return vi;
}

// this function is not locked
// this function is only intend to be internal used
int view_getpos_delta(view_t* vi, view_t* ancestor, int* deltax, int* deltay)
{
	view_t *tmp = vi;
	int dx = 0, dy = 0;
	
	if (!vi || !deltax || !deltay)
		return 1;
	
	for (; vi != ancestor; vi = vi->parent)
	{
		dx += vi->left;
		dy += vi->top;
	}
	*deltax = dx - tmp->left;
	*deltay = dy - tmp->top;
	return 0;
}

int view_get_delta_pos(view_t* vi, view_t* ancestor, int* deltax, int* deltay)
{
	int ret;
	sync_start(&view_ops_sync);
	ret = view_getpos_delta(vi, ancestor, deltax, deltay);
	sync_end(&view_ops_sync);
	return ret;
}

// this function is not locked
// this function is only intend to be internal used
int view_getpos(view_t* vi, view_t* ancestor, int* x, int* y)
{
	int dx = 0, dy = 0;
	if (!vi || !x || !y)
		return 1;

	for (; vi != ancestor; vi = vi->parent)
	{
		dx += vi->left;
		dy += vi->top;
	}
	*x = dx;
	*y = dy;
	return 0;
}

void global_init_viewops(void)
{
	prepare_sync_object(view_ops_sync);
}

void global_destroy_viewops(void)
{
	release_sync_object(view_ops_sync);
}
